﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Ports;
using System.Windows;
using System.Threading;
using System.Windows.Threading;
using System.Collections;

#region
//SerialPort的基本属性：

// BaseStream 获取 SerialPort 对象的基础 Stream 对象

// BaudRate 获取或设置串行波特率

// BreakState 获取或设置中断信号状态

// BytesToRead 获取接收缓冲区中数据的字节数

// BytesToWrite 获取发送缓冲区中数据的字节数

// CDHolding 获取端口的载波检测行的状态

// CtsHolding 获取“可以发送”行的状态

// DataBits 获取或设置每个字节的标准数据位长度

// DiscardNull 获取或设置一个值，该值指示 Null 字节在端口和接收缓冲区之间传输时是否被忽略

// DsrHolding 获取数据设置就绪 (DSR) 信号的状态

// DtrEnable 获取或设置一个值，该值在串行通信过程中启用数据终端就绪 (DTR) 信号

// Encoding 获取或设置传输前后文本转换的字节编码

// Handshake 获取或设置串行端口数据传输的握手协议

// IsOpen 获取一个值，该值指示 SerialPort 对象的打开或关闭状态

// NewLine 获取或设置用于解释 ReadLine( )和WriteLine( )方法调用结束的值

// Parity 获取或设置奇偶校验检查协议

// ParityReplace 获取或设置一个字节，该字节在发生奇偶校验错误时替换数据流中的无效字节

// PortName 获取或设置通信端口，包括但不限于所有可用的 COM 端口

// ReadBufferSize 获取或设置 SerialPort 输入缓冲区的大小

// ReadTimeout 获取或设置读取操作未完成时发生超时之前的毫秒数

// ReceivedBytesThreshold 获取或设置 DataReceived 事件发生前内部输入缓冲区中的字节数

// RtsEnable 获取或设置一个值，该值指示在串行通信中是否启用请求发送 (RTS) 信号

// StopBits 获取或设置每个字节的标准停止位数

// WriteBufferSize 获取或设置串行端口输出缓冲区的大小

// WriteTimeout 获取或设置写入操作未完成时发生超时之前的毫秒数

// SerialPort类的常用方法

// 方 法 名 称 说　　明

// Close 关闭端口连接，将 IsOpen 属性设置为False，并释放内部 Stream 对象

// Open 打开一个新的串行端口连接

// Read 从 SerialPort 输入缓冲区中读取

// ReadByte 从 SerialPort 输入缓冲区中同步读取一个字节

// ReadChar 从 SerialPort 输入缓冲区中同步读取一个字符

// ReadLine 一直读取到输入缓冲区中的 NewLine 值

// ReadTo 一直读取到输入缓冲区中指定 value 的字符串

// Write 已重载。将数据写入串行端口输出缓冲区

// WriteLine 将指定的字符串和 NewLine 值写入输出缓冲区
#endregion
namespace Kmust.Commom
{

    public class KmustSerialPort
    {
        //串口声明
        private static Object thisLock = new Object();
        private static SerialPort Port;

        private static bool recStaus = true;//接收状态字  

        private static bool ComPortIsOpen = false;//COM口开启状态字，在打开/关闭串口中使用，这里没有使用自带的ComPort.IsOpen，因为在串口突然丢失的时候，ComPort.IsOpen会自动false，逻辑混乱  

        private static bool Listening = false;//用于检测是否没有执行完invoke相关操作，仅在单线程收发使用，但是在公共代码区有相关设置，所以未用#define隔离  

        private static bool WaitClose = false;//invoke里判断是否正在关闭串口是否正在关闭串口，执行Application.DoEvents，并阻止再次invoke ,解决关闭串口时，程序假死，具体参见http://news.ccidnet.com/art/32859/20100524/2067861_4.html 仅在单线程收发使用，但是在公共代码区有相关设置，所以未用#define隔离  

        private static bool Sending = false;//正在发送数据状态字  

        private static Thread _ComSend;//发送数据线程  

        private static Thread _ComRev;//接收数据的线程

        private static int sendCount; //已发送总字数

        private static int recvCount;

        static Queue recQueue=new Queue();//接收数据过程中，接收数据线程与数据处理线程直接传递的队列，先进先出  

        public SendSetStr SendSet = new SendSetStr();//发送数据线程传递参数的结构体  

        public delegate void RecvDateHandler(object send, object e);

        private static string recvDateText;

        public static string RecvDateText
        {
            get { return KmustSerialPort.recvDateText; }
            set { KmustSerialPort.recvDateText = value; }
        }

        public struct SendSetStr//发送数据线程传递参数的结构体格式  
        {
            public string SendSetData;//发送的数据  
            public bool? SendSetMode;//发送模式  true时是16进制发送，否则则为二进制发送
        }

        public static bool recvMode;//接收模式

       // private static string recvText;//接收到的数据

        //public static string RecvText
        //{
        //    get
        //    {
        //        string temp = recvText;
        //        recvText = "";
        //        return temp;
        //    }
        //    set
        //    {
        //        recvText = recvText + value;
        //    }
      //  }       //接收到的数据外调函数

        //串口初始化函数，必须调用此函数
        public static bool InitPort()
        {
            try
            {
                //实例化串口对象(默认：COMM1,9600,e,8,1) 
                Port = new SerialPort();
                //更改参数
                Port.PortName = "COM2";
                Port.BaudRate = 9600;
                Port.Parity = Parity.None;
                Port.StopBits = StopBits.One;
                recvMode = false;       //设值串口的接受模式 false为ASCii接收模式，否则为16进制接收模式
                //上述步骤可以用在实例化时调用SerialPort类的重载构造函数
                //SerialPort serialPort = new SerialPort("COM1", 19200, Parity.Odd, StopBits.Two);

                //打开串口(打开串口后不能修改端口名,波特率等参数,修改参数要在串口关闭后修改)
                if (!Port.IsOpen)
                {
                    Port.Open();
                    ComPortIsOpen = true;       //串口打开标志
                    Port.DataReceived += Port_DataReceived;         //串口中断标志
                }
                else
                {
                    MessageBox.Show("Port is open!");
                }
                _ComRev = new Thread(new ThreadStart(ComRec));
   //             _ComRev.Start();
              //  _ComRev.SetApartmentState(ApartmentState.STA);
               // _ComRev.IsBackground = true;
                _ComRev.Start();
                return true;
            }
            catch (Exception e)
            {
                return false;
            }
        }

        static void Port_DataReceived(object sender, SerialDataReceivedEventArgs e) //接收数据 中断只标志有数据需要读取，读取操作在中断外进行  
        {
            //throw new NotImplementedException();
            if (WaitClose) return;//如果正在关闭串口，则直接返回  
            Thread.Sleep(10);//发送和接收均为文本时，接收中为加入判断是否为文字的算法，发送你（C4E3），接收可能识别为C4,E3，可用在这里加延时解决  
            if (recStaus)//如果已经开启接收  
            {
                byte[] recBuffer;//接收缓冲区  
                try
                {
                    recBuffer = new byte[Port.BytesToRead];//接收数据缓存大小  
                    Port.Read(recBuffer, 0, recBuffer.Length);//读取数据  
                    recQueue.Enqueue(recBuffer);//读取数据入列Enqueue（全局）  
                }
                catch
                {
                    if (Port.IsOpen == false)//如果ComPort.IsOpen == false，说明串口已丢失  
                    {
                        SetComLose();//串口丢失后相关设置  
                    }
                    else
                    {
                        MessageBox.Show("无法接收数据，原因未知！");
                    }
                }

            }
            else//暂停接收  
            {
                Port.DiscardInBuffer();//清接收缓存  
            }
        }

     

        public static void ComSend(SendSetStr str)//发送数据 独立线程方法 发送数据时UI可以响应  
        {

            lock (thisLock)//由于send()中的if (Sending == true) return，所以这里不会产生阻塞，如果没有那句，多次启动该线程，会在此处排队  
            {
                Sending = true;//正在发生状态字  
                byte[] sendBuffer = null;//发送数据缓冲区  
                string sendData = str.SendSetData;//复制发送数据，以免发送过程中数据被手动改变  
                if (str.SendSetMode.Value)//16进制发送  
                {
                    try //尝试将发送的数据转为16进制Hex  
                    {
                        sendData = sendData.Replace(" ", "");//去除16进制数据中所有空格  
                        sendData = sendData.Replace("\r", "");//去除16进制数据中所有换行  
                        sendData = sendData.Replace("\n", "");//去除16进制数据中所有换行  
                        if (sendData.Length == 1)//数据长度为1的时候，在数据前补0  
                        {
                            sendData = "0" + sendData;
                        }
                        else if (sendData.Length % 2 != 0)//数据长度为奇数位时，去除最后一位数据  
                        {
                            sendData = sendData.Remove(sendData.Length - 1, 1);
                        }

                        List<string> sendData16 = new List<string>();//将发送的数据，2个合为1个，然后放在该缓存里 如：123456→12,34,56  
                        for (int i = 0; i < sendData.Length; i += 2)
                        {
                            sendData16.Add(sendData.Substring(i, 2));
                        }
                        sendBuffer = new byte[sendData16.Count];//sendBuffer的长度设置为：发送的数据2合1后的字节数  
                        for (int i = 0; i < sendData16.Count; i++)
                        {
                            sendBuffer[i] = (byte)(Convert.ToInt32(sendData16[i], 16));//发送数据改为16进制  
                        }
                    }
                    catch //无法转为16进制时，出现异常  
                    {
                        Sending = false;//关闭正在发送状态  
                        _ComSend.Abort();//终止本线程  
                        return;//输入的16进制数据错误，无法发送，提示后返回    
                    }

                }
                else //ASCII码文本发送  
                {
                    sendBuffer = System.Text.Encoding.Default.GetBytes(sendData);//转码  
                }
                try//尝试发送数据  
                {//如果发送字节数大于1000，则每1000字节发送一次  
                    int sendTimes = (sendBuffer.Length / 1000);//发送次数  
                    for (int i = 0; i < sendTimes; i++)//每次发生1000Bytes  
                    {
                        Port.Write(sendBuffer, i * 1000, 1000);//发送sendBuffer中从第i * 1000字节开始的1000Bytes  
                       // sendCount = sendCount + 1000;
                    }
                    if (sendBuffer.Length % 1000 != 0)//发送字节小于1000Bytes或上面发送剩余的数据  
                    {
                        Port.Write(sendBuffer, sendTimes * 1000, sendBuffer.Length % 1000);
                     //   sendCount = sendCount + sendBuffer.Length % 1000;
                    }
                }
                catch//如果无法发送，产生异常  
                {
                    if (Port.IsOpen == false)//如果ComPort.IsOpen == false，说明串口已丢失  
                    {
                        SetComLose();//串口丢失后的设置  
                    }
                    else
                    {
                        MessageBox.Show("无法发送数据，原因未知！");
                    }
                }
                //sendScrol.ScrollToBottom();//发送数据区滚动到底部  
                Sending = false;//关闭正在发送状态  
                _ComSend.Abort();//终止本线程  
            }

        }

       

        public static void SetComLose()//成功关闭串口或串口丢失后的设置
        {
            WaitClose = true;//;//激活正在关闭状态字，用于在串口接收方法的invoke里判断是否正在关闭串口
         //   MessageBox.Show("串口已丢失");
            if (Port.IsOpen)
            {
                Port.Close();//关闭串口
                ComPortIsOpen = false;      //串口关闭标志
            }
        }

        public static event RecvDateHandler RecvDateEvent;           //数据接收的自定义事件

        public static void  ComRec()//接收线程，窗口初始化中就开始启动运行  
        {
            string recvText;
            while (true)//一直查询串口接收线程中是否有新数据  
            {
                if (recQueue.Count > 0)//当串口接收线程中有新的数据时候，队列中有新进的成员recQueue.Count > 0  
                {
                    string recData;//接收数据转码后缓存  
                    byte[] recBuffer = (byte[])recQueue.Dequeue();//出列Dequeue（全局）  
                    recData = System.Text.Encoding.Default.GetString(recBuffer);//转码  
                    recvText = "";                //清空接收到的数据
                    if (recvMode)//接收模式为ASCII文本模式  
                    {
                        recvText = recData;//加显到接收区  
                    }
                    else
                    {
                        StringBuilder recBuffer16 = new StringBuilder();//定义16进制接收缓存  
                        for (int i = 0; i < recBuffer.Length; i++)
                        {
                            recBuffer16.AppendFormat("{0:X2}" + " ", recBuffer[i]);//X2表示十六进制格式（大写），域宽2位，不足的左边填0。  
                        }
                        recvText = recBuffer16.ToString();//加显到接收区  
                    
                    }
                    // recvCount = Convert.ToInt32(recvText) + recBuffer.Length;//接收数据字节数  
                    RecvDateEvent(Thread.CurrentThread, recvText);
                    RecvDateText = recvText;
                }
                else
                    Thread.Sleep(100);//如果不延时，一直查询，将占用CPU过高  
            }

        }

    }
}
